import ListScrollItem from './ListScrollItem';
import ScrollViewExecuteOreFrame from './scrollViewExecuteOreFrame';

const {
    ccclass,
    property,
} = cc._decorator;


// item模板类型
enum TemplateType {
    Node = 0,
    Prefab = 1,
}

// 滚动方向
enum ScrollDirection {
    Vertical = 1,
    Horizontal,
}

interface GetListData {
    /**
     * @return  获得列表数据总数
     */
    (): any[];
}

interface InitParam {
    getListData: GetListData
}

interface ItemPools {
    /**
     * @description 获得item类型对象池
     * @param type itemtype
     * @return  对象池
     */
    [type: string]: cc.NodePool;
}

@ccclass
export default class ListScrollView extends cc.Component {
    @property({
        type: cc.Enum(TemplateType),
        tooltip: 'item模板类型',
    })
    templateType: TemplateType = TemplateType.Node;
    @property({
        type: cc.Node,
        visible: function () {
            const isVisible = this.templateType == TemplateType.Node;
            if (isVisible && this.itemComponent.length == 0 && this.itemTemplate) {
                this.itemComponent = this.itemTemplate.name;
            }
            return isVisible;
        },
    })
    itemTemplate: cc.Node = null;
    @property({
        type: cc.Prefab,
        visible: function () {
            const isVisible = this.templateType == TemplateType.Prefab;
            if (isVisible && this.itemComponent.length == 0 && this.itemPrefabTemplate) {
                this.itemComponent = this.itemPrefabTemplate.data.name;
            }
            return isVisible;
        },
    })
    itemPrefabTemplate: cc.Prefab = null;
    @property({
        tooltip: 'item脚本名称',
    })
    itemComponent: string = '';
    @property({
        type: cc.Enum(ScrollDirection),
    })
    scrollDirection: ScrollDirection = ScrollDirection.Vertical;
    @property({
        tooltip: 'item是否固定高度',
    })
    isItemFixed: boolean = true;
    @property(ScrollViewExecuteOreFrame)
    frameLoad: ScrollViewExecuteOreFrame = null;
    @property({
        tooltip: '需要初始化显示的item数量',
    })
    initItemCount: number = 0;
    @property(cc.Scrollbar)
    scrollBar: cc.Scrollbar = null;
    @property
    spacing: number = 0;
    @property
    topPadding: number = 0;
    @property
    bottomPadding: number = 0;
    @property
    padding: cc.Vec2 = cc.v2(0, 0);
    @property
    debug: boolean = false;

    private _scrollView: cc.ScrollView = null;
    private _content: cc.Node = null;
    private _delegate: InitParam = null;
    private _inited = false;

    private _scrollPosition = 0;
    private _activeItemIndexRange: cc.Vec2;
    private _itemPools: ItemPools = {};

    private _itemsOffset: Array<number>; // item底部偏移量
    private _itemsSize: Array<number>;  // item尺寸，高度/宽度
    private _activeItems = new Array<ListScrollItem>();  // 可视item
    private _loadEndCb: Function = null;    // 加载完回调

    protected onLoad(): void {
        this._scrollView = this.node.getComponent(cc.ScrollView);
        if (!this._scrollView) {
            this._scrollView = this.node.addComponent(cc.ScrollView);
            this._scrollView.vertical = this.scrollDirection === ScrollDirection.Vertical;
            this._scrollView.horizontal = this.scrollDirection === ScrollDirection.Horizontal;
        }

        this._content = new cc.Node();
        this._content.setAnchorPoint(0, 1);
        this.node.addChild(this._content);
        this._scrollView.content = this._content;
        if (this.scrollBar) {
            if (this.scrollDirection == ScrollDirection.Vertical) {
                this._scrollView.verticalScrollBar = this.scrollBar;
            } else {
                this._scrollView.horizontalScrollBar = this.scrollBar;
            }
        }

        this._initTemplate();

        if (this.debug) this._content.addComponent(cc.Graphics);

        this._inited = true;
        if (this._delegate) this._load();
    }

    protected update(dt: number): void {
        if (this.debug) {
            this.drawDebug();
        }
    }

    protected onEnable(): void {
        this.node.on('scrolling', this._onScrolling, this);
        this.node.on('scroll-ended', this._onScrollEnd, this);
    }

    protected onDisable(): void {
        if (this.scrollBar) this.scrollBar.node.active = false;
        this.node.targetOff(this);
    }

    protected start(): void {
    }

    // 外部接口
    /**
     * @param param 初始化参数
     */
    public init(param: InitParam) {
        let needClear = false;
        if (this._delegate) needClear = true;
        this._delegate = param;
        if (this._inited) {
            if (needClear) this._clear();
            if (this._delegate.getListData().length > 0) this._load();
        }
    }

    /**
     * @description 重新刷新列表数据,会更新item尺寸
     * @param keepPos 保持当前坐标
     */
    public reload(keepPos: boolean = false, cb?: Function) {
        this._loadEndCb = cb;
        this._clear(keepPos);
        if (this._delegate.getListData().length > 0) this._load();
    }

    /**
     * @description 清理列表
     */
    public cleanScroll() {
        this._clear();
        Object.keys(this._itemPools).forEach(key => this._itemPools[key].clear());
    }

    /**
     * @description 刷新可视区item数据,不会改变item尺寸
     */
    public refresh() {
        this._updateActiveItemData();
    }

    /**
     * @description 获取item在scroll中的相对坐标
     * @param index item索引
     * @return  item相对坐标
     */
    public getItemPosInScroll(index: number): cc.Vec2 {
        let sp = this._getItemPosByIndex(index);
        if (this.scrollDirection == ScrollDirection.Vertical) {
            return new cc.Vec2(0, sp);
        } else {
            return new cc.Vec2(-sp, 0);
        }
    }

    /**
     * 移除item后刷新
     * @param removeCount 移除的item个数
     */
    public refreshAfterRemoveItem(removeCount: number = 1) {
        if (!this.isItemFixed) {
            cc.warn('only used with item fixed');
            return;
        };

        this._scrollView.stopAutoScroll();
        if (this._delegate.getListData().length == 0) {
            this._clear();
            return;
        }

        cc.log('# 删除更新前 content size: ', this._content.height, ', 可视items: ', this._activeItemIndexRange.toString());
        // 重置content尺寸
        this._updateContentSize();
        // 设置offset，保持当前滚动位置
        const originOffset = this._scrollView.getScrollOffset();
        const size = this._getFixedItemSize() * removeCount;
        if (this.scrollDirection == ScrollDirection.Vertical) {
            this._content.y = Math.max(originOffset.y - size, 0);
        } else {
            this._content.x = Math.max(originOffset.x - size, 0);
        }
        cc.log('# 删除更新后 content size: ', this._content.height, ', 可视items: ', this._activeItemIndexRange.toString());
        // 刷新显示item，item回重新排序
        this._refreshActiveItems();
        // 更新item数据
        this._updateActiveItemData();
    }

    /**
     * 获得列表item
     */
    public getItemsInScroll(): cc.Node[] {
        if (!this._content) return [];
        return this._content.children;
    }

    /**
     * @description 滚动到指定item
     * @param index item索引
     * @param timeInSecond 滚动时间
     * @param attenuated 惯性减速
     */
    public scrollToItem(index: number, timeInSecond: number = 0.1, attenuated: boolean = true) {
        let pos = this.getItemPosInScroll(index);
        this._scrollView.scrollTo(pos, timeInSecond, attenuated);
    }

    /**
     * @description 添加数据后滚动到头部
     */
    public scrollToTopAfterAddItem(keepPos: boolean = false) {
        this.reload(keepPos);
        this.scrollToItem(0);
    }

    /**
     * @description 添加数据后滚动到底部
     * @param keepPos
     * @param timeInSecond
     * @param attenuated
     */
    public scrollToBottomAfterAddItem(keepPos: boolean = false, timeInSecond: number = 0.1, attenuated: boolean = true) {
        this.reload(keepPos, () => {
            const lastItem = this._content.children[this._content.childrenCount - 1];
            if (lastItem && this._content.height > this._scrollView.node.height) {
                const toPos = cc.v2(lastItem.position.subSelf(cc.v2(0, lastItem.height)));
                this._scrollView.scrollTo(toPos, timeInSecond, attenuated);
            }
        });
    }

    /**
     * @description 滚动到指定坐标
     * @param pos
     */
    public scrollToPos(pos: cc.Vec2, keepPos: boolean = false, timeInsecond: number = 0.1, attenuated: boolean = true) {
        this._scrollView.scrollTo(pos, timeInsecond, attenuated);
    }

    /**
     * @description 获得content
     */
    get content() {
        return this._content;
    }

    /**
     * @description 获得scrollview
     */
    get scrollView() {
        return this._scrollView;
    }

    // 列表处理
    private _initTemplate() {
        if (this.templateType == TemplateType.Prefab) {
            if (this.itemPrefabTemplate.data.getComponent('BaseComponent')) {
                this.itemTemplate = cc.instantiate(this.itemPrefabTemplate);
            } else {
                this.itemTemplate = cc.instantiate(this.itemPrefabTemplate);//ResourceManager.instantiate(this.itemPrefabTemplate);
            }
        }
        if (this.itemComponent.length == 0) {
            this.itemComponent = this.itemTemplate.name;
        } else {
            this.itemComponent = this.itemComponent;
        }

        this.curNodePool.put(this.itemTemplate);
    }

    /**
     *  scroll滚动
     */
    private _onScrolling() {
        if (!this._delegate) return;
        const offset = this._scrollView.getScrollOffset();
        if (this.scrollDirection == ScrollDirection.Vertical) {
            this._scrollPosition = offset.y;
        } else {
            this._scrollPosition = offset.x * -1;
        }

        // refresh active cell with new scroll position
        this._refreshActiveItems();
    }

    private _onScrollEnd() {
        if (!this._delegate) return;

        // this._loadEndCb && this._loadEndCb();
    }

    /**
     * @description 清空列表
     * @param keepPos 是否保持当前坐标
     */
    private _clear(keepPos: boolean = false) {
        if (this._activeItems) {
            while (this._activeItems.length > 0) {
                this._recycleItem(this._activeItems.length - 1);
            }
        }

        this._activeItemIndexRange = cc.v2(-1, -1);
        if (!keepPos) {
            this._scrollPosition = 0;
            if (this._content) {
                this._scrollView.stopAutoScroll();
                this._content.x = 0;
                this._content.y = 0;
            }
        }
    }

    /**
     * @description 加载列表
     */
    private _load() {
        this._updateContentSize();

        const range = this._getActiveItemIndexRange();
        this._activeItemIndexRange = range;

        if (this.frameLoad) {
            this._frameLoad(range.x, range.y);
        } else {
            for (let i = range.x; i <= range.y; i++) {
                this._addItem(i);
            }
            this._onRefreshEnd();
        }
    }

    private _updateContentSize() {
        const dataLen = this._delegate.getListData().length;
        if (dataLen <= 0) return;

        let offset = this.topPadding;
        this._itemsOffset = new Array<number>(dataLen);
        this._itemsSize = new Array<number>(dataLen);
        for (let i = 0; i < dataLen; i++) {
            let size = 0;
            if (this.isItemFixed) {
                size = this._getFixedItemSize();
            } else {
                size = this._getNotFixedItemSize(i);
            }
            this._itemsSize[i] = size;
            offset = size + (i == 0 ? 0 : this.spacing) + offset;
            this._itemsOffset[i] = offset;
        }
        offset += this.bottomPadding;

        if (this.scrollDirection == ScrollDirection.Vertical) {
            this._content.setContentSize(this.node.width, offset);
        } else {
            this._content.setContentSize(offset, this.node.height);
        }
    }

    private _onRefreshEnd() {
        const maxOffset = this._scrollView.getMaxScrollOffset();
        let needRefreshActiveItem = false;
        if (this.scrollDirection == ScrollDirection.Vertical) {
            needRefreshActiveItem = this._scrollPosition > maxOffset.y;
        } else {
            needRefreshActiveItem = this._scrollPosition > maxOffset.x;
        }
        if (needRefreshActiveItem) {
            this._content.y = maxOffset.y//this._scrollPosition//maxOffset.y;
            // this._scrollView.scrollToOffset(maxOffset);
        }

        this._loadEndCb && this._loadEndCb();
        this._resetScrollBar();
    }

    private _resetScrollBar() {
        this._loadEndCb && this._loadEndCb();
        if (!this.scrollBar) return;
        let contentSize = this._content.getContentSize();
        let scrollViewSize = this._scrollView.node.getContentSize();

        let showScrollBar = false;
        if (this.scrollDirection == ScrollDirection.Vertical) {
            showScrollBar = contentSize.height > scrollViewSize.height;
        } else {
            showScrollBar = contentSize.width > scrollViewSize.width;
        }
        this.scrollBar.node.active = showScrollBar;
        showScrollBar ? this.scrollBar.show() : this.scrollBar.hide();
        this._scrollView._updateScrollBar(0);
    }

    private _frameLoad(start: number, end: number) {
        this.frameLoad.executePreFrame(this._getItemGenerator(start, end), 0.1);
    }

    private* _getItemGenerator(start: number, end: number) {
        for (let i = start; i <= end; ++i) {
            yield this._addItem(i);
        }
        this._onRefreshEnd();
    }

    /**
     *  @description  刷新可视item
     */
    private _refreshActiveItems() {
        // 更新scroll中可视item
        const range = this._getActiveItemIndexRange();
        // 检查是否有itme需要更新               
        if (range.equals(this._activeItemIndexRange)) return;

        // 回收超出可视区域的item
        let i = 0;
        while (i < this._activeItems.length) {
            let cell = this._activeItems[i];
            if (cell.dataIndex < range.x || cell.dataIndex > range.y) {
                this._recycleItem(i);
            } else {
                i++;
            }
        }

        // 添加可显示item
        // !TODO: boost this part effecient
        for (let i = range.x; i <= range.y; i++) {
            let needadd = true;
            for (let j = 0; j < this._activeItems.length; j++) {
                if (this._activeItems[j].dataIndex == i) {
                    needadd = false;
                    break;
                }
            }

            if (needadd) this._addItem(i);
        }

        // 根据dataIndex排序
        this._activeItems.sort( (a,b) => a.dataIndex-b.dataIndex );

        // 更新item区域
        this._activeItemIndexRange = range;
        cc.log('更新可视范围： ', {...this._activeItemIndexRange});
    }

    /**
     * @description 回收item
     * @param itemIndex item索引
     */
    private _recycleItem(index: number) {
        // !TODO: need store this cell in node pool
        const item = this._activeItems[index];
        // ResourceManager.releaseNodeRes(item.node);
        this._activeItems.splice(index, 1);
        item.node.removeFromParent(false);
        item.dataIndex = -1;

        if (!this._itemPools[item.itemType]) {
            this._itemPools[item.itemType] = new cc.NodePool();
        }
        let pool = this._itemPools[item.itemType];
        pool.put(item.node);
    }

    /**
     * @description 从对象池中拿item
     * @param itemType 对象池类型
     */
    private _getItemFromPool(itemType: string): ListScrollItem {
        if (this.curNodePool.size() < 1) {
            let node = null;
            if (this.itemTemplate.getComponent('BaseComponent')) {
                node = cc.instantiate(this.itemTemplate);
            } else {
                node = cc.instantiate(this.itemTemplate);//ResourceManager.instantiate(this.itemTemplate);
            }
            this.curNodePool.put(node);
        }
        const item = this.curNodePool.get();
        // ResourceManager.retainNodeRes(item);
        let scrollItem = item.getComponent(ListScrollItem);
        if (!scrollItem) {
            scrollItem = item.addComponent(ListScrollItem);
        }
        return scrollItem;
    }

    /**
     * @description 获得可显示item区域
     */
    private _getActiveItemIndexRange(): cc.Vec2 {
        const maxOffset = this._scrollView.getMaxScrollOffset();
        let startPos = this.scrollDirection == ScrollDirection.Vertical ? Math.min(this._scrollPosition, maxOffset.y) : Math.min(this._scrollPosition, maxOffset.x);
        let endPos = startPos + (this.scrollDirection == ScrollDirection.Vertical ? this.node.height : this.node.width);
        return new cc.Vec2(this._getItemIndexOfPos(startPos), this._getItemIndexOfPos(endPos));
    }

    /**
     * @description 获得item在可视坐标索引
     * @param pos
     */
    private _getItemIndexOfPos(pos: number): number {
        // !TODO: boost this function speed by using binary search
        for (let i = 0; i < this._itemsOffset.length; i++) {
            if (this._itemsOffset[i] >= pos) return i;
        }
        return this._itemsOffset.length - 1;
    }

    /**
     * @description 获取item顶部位置
     * @param index item索引
     * @return  item尺寸
     */
    private _getItemPosByIndex(index: number): number {
        return this._itemsOffset[index] - this._itemsSize[index];
    }

    /**
     * @description 在item添加到content之前获取item尺寸，只有item不定高时会调用
     * @param dataIndex
     * @return  item尺寸
     */
    private _getNotFixedItemSize(dataIndex: number): number {
        const item = this._getItemFromPool(this.itemComponent);
        item.dataIndex = dataIndex;
        item.isResizeBeforeShow = true;
        this._updateItemContent(item);

        let size = 0;
        if (this.scrollDirection === ScrollDirection.Vertical) {
            size = item.node.height;
        } else {
            size = item.node.width;
        }

        this.curNodePool.put(item.node);
        return size;
    }

    /**
     *  @description    获取固定尺寸item的尺寸
     *  @return item尺寸
     */
    private _getFixedItemSize(): number {
        if (this.scrollDirection === ScrollDirection.Vertical) {
            return this.itemTemplate.height;
        } else {
            return this.itemTemplate.width;
        }
    }

    /**
     * @description 添加item
     * @param dataIndex 数据索引
     */
    private _addItem(dataIndex: number) {
        let item = this._getItemFromPool(this.itemComponent);
        item.node.setAnchorPoint(0, 1);
        item.itemType = this.itemComponent;
        item.dataIndex = dataIndex;

        item.enabled = true;
        this._activeItems.push(item);
        this._content.addChild(item.node);
        if (this.scrollDirection == ScrollDirection.Vertical) {
            item.node.x = this.padding.x;
            item.node.y = (this._itemsOffset[item.dataIndex] - this._itemsSize[item.dataIndex]) * -1;
            item.node.setContentSize(this.node.width - this.padding.x - this.padding.y, this._itemsSize[dataIndex]);
        } else {
            item.node.x = (this._itemsOffset[item.dataIndex] - this._itemsSize[item.dataIndex]);
            item.node.y = this.padding.x * -1;
            item.node.setContentSize(this._itemsSize[dataIndex], this.node.height - this.padding.x - this.padding.y);
        }

        this._updateItemContent(item);
    }

    /**
     * @description 更新可视item数据
     */
    private _updateActiveItemData() {
        this._activeItems.forEach(item => {
            this._updateItemContent(item);
        });
    }

    /**
     * @description 更新item数据
     * @param item
     */
    private _updateItemContent(item: ListScrollItem) {
        let data = null;
        if (this._delegate.getListData) {
            data = this._delegate.getListData()[item.dataIndex];
        }
        cc.log('# item', item.dataIndex, ' : ', data)
        item.updateItemData(data);
    }

    drawDebug() {
        const mask = this.node.getComponent(cc.Mask);
        if (mask) mask.enabled = false;
        const graphics = this._content.getComponent(cc.Graphics);
        graphics.clear();
        graphics.fillColor = cc.Color.YELLOW;
        graphics.fillRect(0, -this._content.height, this._content.width, this._content.height);

        graphics.strokeColor = cc.Color.RED;
        graphics.lineWidth = 2;
        this._activeItems.forEach(item => {
            const box = item.node.getBoundingBox();
            graphics.rect(box.x, box.y, box.width, box.height);
            graphics.stroke();
        })
    }

    // 内部对象
    get curNodePool(): cc.NodePool {
        if (!this._itemPools[this.itemComponent]) this._itemPools[this.itemComponent] = new cc.NodePool();
        return this._itemPools[this.itemComponent];
    }
}